﻿using System;
using System.ComponentModel;
using System.Device.Location;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net.NetworkInformation;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Imaging;
using System.Linq;
using Coding4Fun.Phone.Controls;
using Coding4Fun.Phone.Site.Controls;
using ExifLib;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Controls.Maps;
using Microsoft.Phone.Controls.Maps.Core;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Tasks;

namespace Coding4Fun.WheresMyCar
{
    [DataContract]
    public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
    {
        protected int MinAccuracy { get; set; }
        protected GeoCoordinate LocationPrev { get; set; }
        private RouteHelper helper;

        private GeoCoordinateWatcher _gps;
        private CameraCaptureTask photoTask;
        protected Map _map;
        private bool _online = true;
        private bool _isPhotoTaskShowing;

        // Constructor
        public MainPage()
        {
            InitializeComponent();


            if (!IsolatedStorageSettings.ApplicationSettings.Contains("FirstTime"))
            {
                Dispatcher.BeginInvoke(() =>
                {
                    var prompt = new AboutPrompt
                    {
                        Title = "Location Aware",
                        Body = new TextBlock
                        {
                            Text = App.LocAwareMessage,
                            TextWrapping = TextWrapping.Wrap,
                            VerticalAlignment = VerticalAlignment.Center
                        },
                        VersionNumber = ""
                    };

                    prompt.Show();
                });

                IsolatedStorageSettings.ApplicationSettings.Add("FirstTime", "false");
            }

            photoTask = new CameraCaptureTask();
            photoTask.Completed += OnCameraCaptureCompleted;

            DataContext = this;

            _gps = ((App)Application.Current).GPS;
            _gps.StatusChanged += GpsStatusChanged;
            _gps.PositionChanged += GpsPositionChanged;

            Settings = WheresTheCarSettings.Instance;
            Status = _gps.Status.ToString();
            IsGpsLocked = false;
            IsGpsEnabled = (_gps.Permission != GeoPositionPermission.Denied && _gps.Status != GeoPositionStatus.Disabled);
            MinAccuracy = 1000;
            Accuracy = int.MaxValue;

            UpdateLocation();
            NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
            CheckNetwork();
        }

        void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            CheckNetwork();
        }

        private void CheckNetwork()
        {
            if (!NetworkInterface.GetIsNetworkAvailable() && _online)
            {
                _online = false;
                MessageBox.Show(App.NoNetworkError);
            }
            else if (NetworkInterface.GetIsNetworkAvailable() && !_online)
            {
                _online = true;
                MessageBox.Show(App.NetworkFoundMessage);
            }
        }

        #region Event handlers

        void GpsStatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            Status = e.Status.ToString();

            if (e.Status == GeoPositionStatus.Disabled)
                MessageBox.Show(App.GpsDeniedError);
        }

        void GpsPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            UpdateLocation();
        }

        void UpdateLocation()
        {
            if (_gps.Position == null || _gps.Position.Location == null || _gps.Position.Location.IsUnknown) return;

            LocationPrev = CurrentLocation;
            CurrentLocation = _gps.Position.Location;
            IsGpsEnabled = true;
            var acc = this.Accuracy;
            this.Accuracy = _gps.Position.Location.HorizontalAccuracy;
            IsGpsLocked = Accuracy <= MinAccuracy;

            // Re-route only if accuracy has increased
            if (IsGpsLocked && this.Accuracy < acc && helper != null)
            {
                helper.CalculateRouteToCar(CurrentLocation);
            }

            if (!Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
            {
                Settings.HeadingToCar = Utility.CalculateHeading(CurrentLocation, Settings.ParkingSpot);
                Settings.DistanceToCar = Utility.DistanceToCar(Settings.ParkingSpot, CurrentLocation);
            }

            if (!Utility.IsCoordNullOrUnknown(LocationPrev))
                HeadingOfMotion = Utility.CalculateHeading(LocationPrev, CurrentLocation);
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (Settings != null)
            {
                Settings.Refresh();
                RaisePropertyChanged("Settings");
            }

            ResetPage();

            // Use map image if the parking spot is set
            if (!Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
                MapImage = "map.jpg";

            // Default location if GPS not ready yet
            if (Utility.IsCoordNullOrUnknown(CurrentLocation))
                CurrentLocation = (GeoCoordinate)IsolatedStorageSettings.ApplicationSettings["location"];
        }

        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);
            this.Settings.Save();
            IsolatedStorageSettings.ApplicationSettings["location"] = this.CurrentLocation;
        }

        private void ResetPage()
        {
            ApplicationBar.Buttons.Clear();
            if (!Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
            {
                if (_map == null)
                {
                    _map = Utility.CreateMapWithPushpins();
                    _map.SetBinding(MapBase.CenterProperty, new Binding("Settings.ParkingSpot"));
                    _map.MouseLeftButtonDown += MapClick;
                    helper = new RouteHelper(_map, Settings);

                    MapGrid.Children.Insert(0, _map);
                }

                // In case of settings change...
                _map.Mode = Settings.UseAerialMode ? (MapMode)new AerialMode() : new RoadMode();

                IsGpsEnabled = (_gps.Permission != GeoPositionPermission.Denied && _gps.Status != GeoPositionStatus.Disabled);

                if (IsGpsEnabled)
                    GpsPositionChanged(this, null);

                var reset = new ApplicationBarIconButton { Text = "reset", IconUri = new Uri("/Images/dark/appbar.sync.rest.png", UriKind.Relative) };
                reset.Click += menuResetClick;

                ApplicationBar.Buttons.Add(reset);

                helper.CalculateRouteToCar(CurrentLocation);
            }
            else
            {
                MapGrid.Children.Remove(_map);
                _map = null;

                var park = new ApplicationBarIconButton { Text = "park", IconUri = new Uri("/Images/dark/car2.png", UriKind.Relative) };
                park.Click += MapClick;

                var camera = new ApplicationBarIconButton { Text = "camera", IconUri = new Uri("/Images/dark/appbar.feature.camera.rest.png", UriKind.Relative) };
                camera.Click += menuPhotoClick;

                ApplicationBar.Buttons.Add(park);
                ApplicationBar.Buttons.Add(camera);
            }

            if (Debugger.IsAttached)
            {
                var diag = new ApplicationBarIconButton { Text = "diag", IconUri = new Uri("/Images/dark/arrow.png", UriKind.Relative) };
                diag.Click += diag_Click;
                ApplicationBar.Buttons.Add(diag);
            }
        }

        void diag_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Accuracy: " + Accuracy +
                "\nPermission: " + _gps.Permission +
                "\nStatus: " + Status +
                "\nHeading to car: " + Settings.HeadingToCar +
                "\nHeading of motion: " + HeadingOfMotion);
        }

        private void MapClick(object sender, EventArgs e)
        {
            if (!IsGpsEnabled)
                MessageBox.Show(App.GpsDeniedError);
            else if (Accuracy > MinAccuracy)
                MessageBox.Show(string.Format(App.GpsLockMessage, MinAccuracy, Accuracy));
            else
            {
                if (Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
                    NavigationService.Navigate(new Uri("/MapSet.xaml", UriKind.Relative));
                else
                    NavigationService.Navigate(new Uri("/MapPage.xaml", UriKind.Relative));
            }
        }

        private void PhotoClick(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(Settings.Photo))
                NavigationService.Navigate(new Uri("/Media.xaml", UriKind.Relative));
            else
                ShowPhotoTask();
        }


        private void menuPhotoClick(object sender, EventArgs e)
        {
            ShowPhotoTask();
        }

        private void ShowPhotoTask()
        {
            if (_isPhotoTaskShowing)
                return;

            _isPhotoTaskShowing = true;
            photoTask.Show();
        }

        private void menuAboutClick(object sender, EventArgs e)
        {
            var about = new Coding4FunAboutPrompt();
            about.Show("Arian Kulp", "ArianKulp", "ariankulp@gmail.com", "http://www.ariankulp.com");
        }

        private void menuSettingsClick(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/SettingsPage.xaml", UriKind.Relative));
        }

        private void menuResetClick(object sender, EventArgs e)
        {
            if (MessageBox.Show(App.ClearParkingPrompt, string.Empty, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                Settings.ParkingSpot = null;
                Settings.Photo = null;
                ImageConverter.Cache.Clear();
                this.Settings.Save();
                ResetPage();
                MapImage = null;
            }
        }
        #endregion

        #region Photo Utilities
        void OnCameraCaptureCompleted(object sender, PhotoResult e)
        {
            _isPhotoTaskShowing = false;

            if (e.TaskResult == TaskResult.OK)
            {
                e.ChosenPhoto.Position = 0;

                // figure out the orientation from EXIF data
                e.ChosenPhoto.Position = 0;
                JpegInfo info = ExifReader.ReadJpeg(e.ChosenPhoto, e.OriginalFileName);

                int _angle = 0;

                switch (info.Orientation)
                {
                    case ExifOrientation.TopLeft:
                    case ExifOrientation.Undefined:
                        _angle = 0;
                        break;
                    case ExifOrientation.TopRight:
                        _angle = 90;
                        break;
                    case ExifOrientation.BottomRight:
                        _angle = 180;
                        break;
                    case ExifOrientation.BottomLeft:
                        _angle = 270;
                        break;
                }

                Stream capturedImage;
                if (_angle > 0d)
                {
                    capturedImage = RotateStream(e.ChosenPhoto, _angle);
                }
                else
                {
                    capturedImage = e.ChosenPhoto;
                }

                var bi = new BitmapImage();
                bi.SetSource(capturedImage);

                SavePhoto(new WriteableBitmap(bi));

                Settings.Photo = "parking.jpg";
                Settings.Save();
            }
        }

        private Stream RotateStream(Stream stream, int angle)
        {
            stream.Position = 0;
            if (angle % 90 != 0 || angle < 0) throw new ArgumentException();
            if (angle % 360 == 0) return stream;

            BitmapImage bitmap = new BitmapImage();
            bitmap.SetSource(stream);
            WriteableBitmap wbSource = new WriteableBitmap(bitmap);

            WriteableBitmap wbTarget = null;
            if (angle % 180 == 0)
            {
                wbTarget = new WriteableBitmap(wbSource.PixelWidth, wbSource.PixelHeight);
            }
            else
            {
                wbTarget = new WriteableBitmap(wbSource.PixelHeight, wbSource.PixelWidth);
            }

            for (int x = 0; x < wbSource.PixelWidth; x++)
            {
                for (int y = 0; y < wbSource.PixelHeight; y++)
                {
                    switch (angle % 360)
                    {
                        case 90:
                            wbTarget.Pixels[(wbSource.PixelHeight - y - 1) + x * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                        case 180:
                            wbTarget.Pixels[(wbSource.PixelWidth - x - 1) + (wbSource.PixelHeight - y - 1) * wbSource.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                        case 270:
                            wbTarget.Pixels[y + (wbSource.PixelWidth - x - 1) * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
                            break;
                    }
                }
            }
            MemoryStream targetStream = new MemoryStream();
            wbTarget.SaveJpeg(targetStream, wbTarget.PixelWidth, wbTarget.PixelHeight, 0, 100);
            return targetStream;
        }

        void SavePhoto(WriteableBitmap bmp)
        {
            // Create virtual store and file stream
            using (var myStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                // Encode WriteableBitmap object to a JPEG stream.
                using (var myFileStream = myStore.OpenFile("parking.jpg", FileMode.Create))
                {
                    double maxW = 1024.0, maxH = 768.0, ratio;

                    if (bmp.PixelWidth > maxW)
                        ratio = maxW / bmp.PixelWidth;
                    else
                        ratio = maxH / bmp.PixelHeight;

                    bmp.SaveJpeg(myFileStream, (int)(bmp.PixelWidth * ratio), (int)(bmp.PixelHeight * ratio), 0, 90);

                    // Clear from cache
                    if (ImageConverter.Cache.ContainsKey("parking.jpg")) ImageConverter.Cache.Remove("parking.jpg");
                }
            }
        }
        #endregion

        #region Public properties

        #region IsGpsLocked (INotifyPropertyChanged Property)
        private bool _isGpsLocked;

        public bool IsGpsLocked
        {
            get { return _isGpsLocked; }
            set
            {
                _isGpsLocked = value;
                RaisePropertyChanged("IsGpsLocked");
            }
        }
        #endregion

        #region IsGpsEnabled (INotifyPropertyChanged Property)
        private bool _isGpsEnabled;

        public bool IsGpsEnabled
        {
            get { return _isGpsEnabled; }
            set
            {
                _isGpsEnabled = value;
                RaisePropertyChanged("IsGpsEnabled");
            }
        }
        #endregion

        #region MapImage (INotifyPropertyChanged Property)
        private string _mapImage;

        public string MapImage
        {
            get { return _mapImage; }
            set
            {
                _mapImage = value;
                RaisePropertyChanged("MapImage");
            }
        }
        #endregion

        #region Settings (INotifyPropertyChanged Property)
        private WheresTheCarSettings _settings;

        public WheresTheCarSettings Settings
        {
            get { return _settings; }
            set
            {
                _settings = value;
                RaisePropertyChanged("Settings");
            }
        }
        #endregion

        #region Accuracy (INotifyPropertyChanged Property)
        private double _accuracy;

        public double Accuracy
        {
            get { return _accuracy; }
            set
            {
                _accuracy = value;
                RaisePropertyChanged("Accuracy");
            }
        }
        #endregion

        #region Status (INotifyPropertyChanged Property)
        private string _status;

        public string Status
        {
            get { return _status; }
            set
            {
                _status = value;
                RaisePropertyChanged("Status");
            }
        }
        #endregion

        #region CurrentLocation (INotifyPropertyChanged Property)
        private GeoCoordinate _currentLocation;
        public GeoCoordinate CurrentLocation
        {
            get { return _currentLocation; }
            set
            {
                _currentLocation = value;
                RaisePropertyChanged("CurrentLocation");
            }
        }
        #endregion

        #region Distance (INotifyPropertyChanged Property)
        private double _distance;

        public double Distance
        {
            get { return _distance; }
            set
            {
                _distance = value;
                RaisePropertyChanged("Distance");
            }
        }
        #endregion

        #region HeadingOfMotion (INotifyPropertyChanged Property)
        private double _headingOfMotion;

        public double HeadingOfMotion
        {
            get { return _headingOfMotion; }
            set
            {
                _headingOfMotion = value;
                RaisePropertyChanged("HeadingOfMotion");
            }
        }
        #endregion

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            Dispatcher.BeginInvoke(() =>
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            });
        }
        #endregion

    }
}
